package sketch;

import randoop.RConstructor;
import randoop.StatementKind;
import sketch.util.Checker;
import sketch.util.RandoopUtil;
import sketch.util.ReflectionExecutionOutcome;
import sketch.util.ReflectiveExecutions;
import sketch.util.SketchUtil;

/**
 * This class is designed as an immutable class.
 * */
public final class ExecutableSequenceContent {
	
	public final Object receiver;
	
	public final int receiverId;
	
	public final StatementKind statement;
	
	public final Object[] args;
	
	//the outcome of executing this method
	private ReflectionExecutionOutcome executeOutcome = null;
	
	public ExecutableSequenceContent(Object receiver, StatementKind statement, Object...args) {
		Checker.checkNull(statement, "The statement could not be null.");
		Checker.checkNull(args, "The arguments could not be null.");
		//check the validity
		this.checkValidity(statement, receiver, args);
		//assign
		this.receiver = receiver;
		this.receiverId = System.identityHashCode(receiver);
		this.statement = statement;
		//copy arguments
		this.args = new Object[args.length];
		for(int i = 0; i < args.length; i++) {
			this.args[i] = args[i];
		}
	}
	
	public ExecutableSequenceContent clone(Object receiver) {
		return new ExecutableSequenceContent(receiver, this.statement, this.args);
	}
	
	public ReflectionExecutionOutcome excute() {
		this.executeOutcome = ReflectiveExecutions.reflectiveExecute(this.receiver, this.statement, this.args);
		return this.executeOutcome;
	}
	
	public ReflectionExecutionOutcome getExecutionOutcome() {
		Checker.checkNull(this.executeOutcome, "Please call execute() first.");
		return this.executeOutcome;
	}
	
	private void checkValidity(StatementKind statement, Object receiver, Object...args) {
		int argumentNum = RandoopUtil.getParamNum(statement);
		Checker.checkTrue(argumentNum == args.length, "The argument number of statement: " + statement +
				" is: " + argumentNum + ", while given: " + args.length + " arguments");
		if(!RandoopUtil.isStaticMethod(statement) && !(statement instanceof RConstructor)) {
			Checker.checkNull(receiver, "The receiver object for statement: " + statement + " could not be null!");
		}
	}
	
	//should the method check the correctness of result?
	public String toCodeString(boolean assertResult) {
		Checker.checkNull(this.executeOutcome, "Please call execute() first to obtain the execution result.");
		StringBuilder sb = new StringBuilder();
		
		if(RandoopUtil.isStaticMethod(statement)) {
			sb.append(RandoopUtil.getDeclaringClass(statement).getName());
		} else {
			if(statement instanceof RConstructor) {
				throw new UnsupportedOperationException("not support the case when statement is a constructor!");
			} else {
			    sb.append(SketchSequence.RECEIVER);
			}
		}
		
		sb.append(".");
		sb.append(RandoopUtil.getMethodName(statement));
		
		sb.append("(");
		for(int i = 0; i < this.args.length; i++) {
			if(i != 0) {
				sb.append(",");
			}
			sb.append(this.args[i]);
		}
		sb.append(")");
		
		if(!assertResult) {
			return sb.toString();
		}
		
		boolean isPrimitiveReturnType = SketchUtil.isPrimitiveOrBoxingType(RandoopUtil.getReturnType(statement));
		if(isPrimitiveReturnType) {
			return "assertEquals(" + sb.toString() + ", " + this.executeOutcome.getReturnObject() + ")";
		} else {
			if(RandoopUtil.getReturnType(statement).equals(void.class)) {
				throw new RuntimeException("Can not assert result of a void type: " + statement);
			} else {
				if(this.executeOutcome.getReturnObject() == null) {
					return "assertEquals(" + sb.toString() + ", " + "null)";
				} else {
					return "assertEquals(" + sb.toString() + " != null)";
				}
			}
		}
		
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Receiver object id: ");
		sb.append(System.identityHashCode(this.receiver));
		sb.append(", call: ");
		sb.append(this.statement);
		sb.append(" with args: ");
		sb.append("[");
		for(int i = 0; i < this.args.length; i++) {
			if (i != 0) {
				sb.append(",");
			}
			sb.append(args[i]);
		}
		sb.append("]");
		sb.append(", result: ");
		if (this.executeOutcome != null) {
		  sb.append(this.executeOutcome.getReturnObject());
		} else {
			sb.append(" not executed now!");
		}
		return sb.toString();
	}
}